use serde_json::Value;
use std::{borrow::Borrow, pin::Pin, str::FromStr, sync::Arc};
use wither::{
    bson::{doc, oid::ObjectId, Bson, DateTime, Document},
    mongodb::options::{FindOneAndUpdateOptions, ReturnDocument, UpdateModifications},
    Model,
};

use super::{like_list_service::LikeListService, user_service::UserService};
use crate::{
    errors::cons_error,
    mdb::{MClient, ModelEx, MDB},
    models::{
        assets::AssetsModel,
        club::{ClubModel, ClubTag},
        club_activity::{ClubActivityLocation, ClubActivityModel},
        club_log::{ClubLogModel, ClubLogType},
        like_list::{LikeListContent, LikeListModel},
        page::PageModel,
        user::{NormalUserModel, SimpleUser, SimpleUserModel, UserActor, UserModel, UserRole},
        watch_record::{WatchRecordContent, WatchRecordModel},
        Assets,
    },
    utils::{
        db_utils::{build_filter, UpdateField, COMMON_NO_REGEXP},
        strings::text_cut_all,
        traits::UpdateModelData,
    },
    AppError, AppResult, Config, Pagination, PaginationQuery,
};
use serde::{Deserialize, Serialize};

mod serde_default {
    use crate::GLOBAL_CONFIG;

    pub fn create_club_favicon() -> Option<String> {
        Some(
            GLOBAL_CONFIG
                .read()
                .unwrap()
                .as_ref()
                .unwrap()
                .app
                .default_club_favicon
                .clone(),
        )
    }
    pub fn create_club_primary_color() -> String {
        "#FFFFFF".to_string()
    }
    pub fn create_club_inner_visible() -> bool {
        true
    }
    pub fn create_club_outer_visible() -> bool {
        true
    }
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct ClubService {
    mdb: MDB,
    m_client: MClient,
    config: Arc<Config>,
    user_service: Arc<UserService>,
    like_list_service: Arc<LikeListService>,
}

impl ClubService {
    pub fn new(
        config: Arc<Config>,
        mdb: MDB,
        m_client: MClient,
        user_service: Arc<UserService>,
        like_list_service: Arc<LikeListService>,
    ) -> Self {
        ClubService {
            mdb,
            m_client,
            config,
            user_service,
            like_list_service,
        }
    }

    pub async fn find_all_clubs(&self, req: &FindAllClubsReq) -> AppResult<Pagination<ClubModel>> {
        let mut filter = build_filter(&req.filter, &COMMON_NO_REGEXP).unwrap_or_default();
        if !filter.contains_key("valid") {
            filter.insert("valid", true);
        }
        ClubModel::find_and_pagination(self.mdb.clone(), filter, &req.page).await
    }

    pub async fn club_detail<T: Into<ObjectId> + Send>(&self, club_id: T) -> AppResult<ClubModel> {
        ClubModel::find_by_id(&*self.mdb.clone(), club_id, None)
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))
    }

    /// ## create_club
    /// 创建社团
    pub async fn create_club(&self, req: CreateClubReq) -> AppResult<ClubModel> {
        let mut club_model: ClubModel = req.into_club_model(self.mdb.clone()).await?;
        club_model.save(&*self.mdb.clone(), None).await?;
        Ok(club_model)
    }

    /// ## delete_club
    /// 删除一个社团
    pub async fn delete_club<T: Into<ObjectId>>(&self, club_id: T) -> AppResult<bool> {
        let club_oid: ObjectId = club_id.into();
        let club_coll = ClubModel::collection(&*self.mdb.clone());
        let res = club_coll
            .delete_one(
                doc! {
                    "_id":club_oid
                },
                None,
            )
            .await?;
        Ok(res.deleted_count > 0)
    }

    pub async fn update_club<T: Into<ObjectId>>(
        &self,
        club_id: T,
        req: &UpdateClubReq,
    ) -> AppResult<ClubModel> {
        let oid: ObjectId = club_id.into();
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        let res = ClubModel::find_one_and_update(
            &*self.mdb.clone(),
            doc! {"_id":oid},
            UpdateModifications::Document(
                doc! {"$set":req.clone().to_document(self.mdb.clone()).await?},
            ),
            Some(options),
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        Ok(res)
    }

    pub async fn add_log<U: Into<Option<SimpleUser>>, T: Into<ClubLogType>, C: Into<Bson>>(
        &self,
        user: U,
        log_type: T,
        content: C,
    ) -> AppResult<ClubLogModel> {
        let mut res = ClubLogModel::new(log_type, content, user);
        res.save(&*self.mdb.clone(), None).await?;
        Ok(res)
    }

    /// ## upsert_user_in_club
    /// 更新或创建一个用户在社团中的角色
    pub async fn upsert_user_in_club<T: Into<ObjectId> + Send, C: Into<ObjectId>>(
        &self,
        user_id: T,
        club_id: C,
        role: u8,
    ) -> AppResult<UserModel> {
        let coll = UserModel::collection(&*self.mdb.clone());
        let actor = UserActor {
            club_id: Some(club_id.into()),
            role,
            ..Default::default()
        };
        let res = coll
            .find_one_and_update(
                doc! {"_id":user_id.into()},
                doc! {"$push":{"actors":actor}},
                FindOneAndUpdateOptions::builder()
                    .return_document(ReturnDocument::After)
                    .build(),
            )
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        Ok(UserModel::instance_from_document(res)?)
    }

    /// ## remove_user_from_club
    /// 将用户从一个社团中移除出去
    pub async fn remove_user_from_club<T: Into<ObjectId>, C: Into<ObjectId>>(
        &self,
        user_id: T,
        club_id: C,
    ) -> AppResult<bool> {
        let coll = UserModel::collection(&*self.mdb.clone());
        let res = coll
            .update_one(
                doc! {"_id":user_id.into()},
                doc! {"$pull":{"actors.club_id":club_id.into()}},
                None,
            )
            .await?;
        Ok(res.modified_count > 0)
    }

    /// ## update_user_actor_in_club
    /// 更新用户在社团的角色
    /// TODO: 使用事务
    pub async fn update_user_actor_in_club<
        T: Into<ObjectId> + Send,
        C: Into<ObjectId>,
        R: Into<u8>,
    >(
        &self,
        user_id: T,
        club_id: C,
        role: u8,
    ) -> AppResult<bool> {
        let club_id: ObjectId = club_id.into();
        let role: u8 = role;
        if role >= UserRole::Admin as u8 {
            return Err(AppError::ConsError(cons_error::MSG_REQUIRE_AUTHORIZE));
        }
        let actors = UserRole::from_usize(role as usize)
            .iter()
            .map(|v| UserActor {
                club_id: Some(club_id),
                role: (*v) as u8,
                ..Default::default()
            })
            .collect::<Vec<_>>();
        let mut user = UserModel::find_by_id(&*self.mdb.clone(), user_id, None)
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        let keys = user
            .actors
            .iter()
            .enumerate()
            .filter_map(|(key, value)| {
                if value
                    .club_id
                    .as_ref()
                    .map(|v| v == &club_id)
                    .unwrap_or(false)
                {
                    Some(key)
                } else {
                    None
                }
            })
            .collect::<Vec<_>>();
        for key in keys {
            user.actors.remove(key);
        }
        user.actors.extend(actors);
        user.save(&*self.mdb.clone(), None).await?;
        Ok(true)
    }
    /// ## add_user_to_admin
    ///  添加用户到管理员
    pub async fn add_user_to_admin<T: Into<ObjectId>>(&self, user_id: T) -> AppResult<bool> {
        let coll = UserModel::collection(&*self.mdb.clone());
        let actor = UserActor {
            role: UserRole::Admin as u8,
            ..Default::default()
        };

        let res = coll
            .update_one(
                doc! {"_id":user_id.into(),"actors.role":{"$not":{"$eq":UserRole::Admin}}},
                doc! {"$addToSet":{"actors":actor}},
                None,
            )
            .await?;
        Ok(res.modified_count > 0)
    }

    /// ## remove_user_from_admin
    /// 将用户移出管理员
    pub async fn remove_user_from_admin<T: Into<ObjectId>>(&self, user_id: T) -> AppResult<bool> {
        let coll = UserModel::collection(&*self.mdb.clone());
        let actor = UserActor {
            role: UserRole::Admin as u8,
            ..Default::default()
        };
        let res = coll
            .update_one(
                doc! {"_id":user_id.into()},
                doc! {"$pull":{"actors":actor}},
                None,
            )
            .await?;
        Ok(res.modified_count > 0)
    }

    /// ## find_all_member_in_club
    /// 查询社团中所有的成员
    pub async fn find_all_member_in_club<T: Into<ObjectId>, W: Into<Document>>(
        &self,
        club_id: T,
        filter: W,
        query: &PaginationQuery,
    ) -> AppResult<Pagination<ClubMember>> {
        let mut filter: Document = filter.into();
        let club_id: ObjectId = club_id.into();
        filter.insert("actors.club_id", &club_id);
        let res = UserModel::find_and_pagination(self.mdb.clone(), filter, &*query)
            .await?
            .map(|f| ClubMember::from_user(f.clone(), &club_id));
        Ok(res)
    }

    /// ## 检查社团中是否还有社长
    pub async fn check_club_has_leader<T: Into<ObjectId>>(&self, club_id: T) -> AppResult<bool> {
        let res = self
            .find_all_member_in_club(
                club_id,
                doc! {
                    "actors.role":{
                        "$gte":UserRole::Leader as i32
                    }
                },
                &PaginationQuery {
                    ..Default::default()
                },
            )
            .await?;

        Ok(res.total_count > 0)
    }

    /// ## 设置社团为隐藏状态
    pub async fn set_club_to_hidden<T: Into<ObjectId>>(&self, club_id: T) -> AppResult<ClubModel> {
        let coll = ClubModel::collection(&*self.mdb);
        let club_id: ObjectId = club_id.into();
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        let club_model = coll
            .find_one_and_update(
                doc! {
                    "_id":club_id
                },
                doc! {
                    "$set":{
                        "valid":false
                    }
                },
                options,
            )
            .await?
            .map(ClubModel::instance_from_document)
            .and_then(Result::ok)
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;

        Ok(club_model)
    }

    pub async fn user_like_club<U: Into<ObjectId>, C: Into<ObjectId>>(
        &self,
        user_id: U,
        club_id: C,
    ) -> AppResult<LikeListModel> {
        let content = LikeListContent::Club {
            source_id: user_id.into(),
            target_id: club_id.into(),
        };
        self.like_list_service.save_like_content(content).await
    }

    pub async fn find_one_club_activity<T: Into<Option<ObjectId>>, F: Into<Option<Document>>>(
        &self,
        activity_id: T,
        filter: F,
    ) -> AppResult<Option<ClubActivityModel>> {
        let mut filter: Document = filter.into().unwrap_or_default();
        let club_id: Option<ObjectId> = activity_id.into();
        if let Some(oid) = club_id {
            filter.insert("_id", oid);
        }
        Ok(ClubActivityModel::find_one(&*self.mdb, filter, None).await?)
    }
    /// ## 搜索社团活动
    pub async fn search_club_activitys<
        T: Into<Option<ObjectId>>,
        F: Into<Option<Document>>,
        Q: Borrow<PaginationQuery>,
    >(
        &self,
        club_id: T,
        filter: F,
        query: Q,
    ) -> AppResult<Pagination<ClubActivityModel>> {
        let mut filter: Document = filter.into().unwrap_or_default();
        let club_id: Option<ObjectId> = club_id.into();
        if let Some(club_id) = club_id {
            filter.insert("_id", club_id);
        }
        filter.insert("valid", true);

        ClubActivityModel::find_and_pagination(self.mdb.clone(), filter, query.borrow()).await
    }

    /// ## 创建社团活动
    pub async fn create_club_activity<T: Into<CreateClubActivitySvrReq>>(
        &self,
        req: T,
    ) -> AppResult<ClubActivityModel> {
        let mut activity = ClubActivityModel::from(req.into());
        activity.save(&*self.mdb, None).await?;
        Ok(activity)
    }

    /// ## 更新社团活动
    pub async fn update_club_activity<T: Into<UpdateClubActivitySvrReq>>(
        &self,
        req: T,
    ) -> AppResult<ClubActivityModel> {
        let update_req: UpdateClubActivitySvrReq = req.into();
        let filter = update_req.get_update_filter_doc()?;
        let set = update_req.get_update_set_doc()?;
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        let res = ClubActivityModel::find_one_and_update(
            &*self.mdb,
            filter,
            doc! {
                "$set":set,
            },
            options,
        )
        .await?
        .ok_or(AppError::ConsError(
            cons_error::MSG_CLUB_ACTIVITY_IS_NOT_FOUND,
        ))?;

        Ok(res)
    }

    /// ## 删除社团活动
    pub async fn delete_club_activity<T: Into<ObjectId>>(
        &self,
        activity_id: T,
    ) -> AppResult<ClubActivityModel> {
        let acitivty_id: ObjectId = activity_id.into();
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        let res = ClubActivityModel::find_one_and_update(
            &*self.mdb,
            doc! {"_id":acitivty_id,"valid":true},
            doc! {"valid":false},
            options,
        )
        .await?
        .ok_or(AppError::ConsError(
            cons_error::MSG_CLUB_ACTIVITY_IS_NOT_FOUND,
        ))?;
        Ok(res)
    }

    /// ## 记录社团的观看数
    pub async fn watch_club_record<T: Into<ObjectId>, U: Into<SimpleUserModel>>(
        &self,
        club_id: T,
        user: U,
    ) -> AppResult<ClubModel> {
        let club_id: ObjectId = club_id.into();
        let mut club = self.club_detail(club_id).await?;
        let coll = WatchRecordModel::collection(&*self.mdb);
        let options = FindOneAndUpdateOptions::builder()
            .upsert(true)
            .return_document(ReturnDocument::Before)
            .build();
        let model_data =
            WatchRecordModel::record_user_watch(user, WatchRecordContent::Club { club_id });
        let docuemtn_data = model_data.document_from_instance()?;
        let filter = model_data.get_filter_document()?;

        let res = coll
            .find_one_and_update(
                filter,
                doc! {
                    "$set":{},
                    "$setOnInsert":docuemtn_data
                },
                options,
            )
            .await?;

        if res.is_some() {
            return Err(AppError::forbidden(cons_error::MSG_ALREADY_WATCH));
        }
        // 这里不需要关注数据竞争，因为上面的find_one_and_update是原子性的
        club.watch_count += 1;
        club.daily_count += 1;
        club.save(&*self.mdb, None).await?;

        Ok(club)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FindAllClubsReq {
    pub filter: Value,
    pub page: PaginationQuery,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateClubReq {
    pub name: String,
    #[serde(default = "serde_default::create_club_primary_color")]
    pub primary_color: String,
    pub page: Option<PageModel>,
    pub location: Option<String>,
    pub address: Option<String>,
    pub introduction: Option<String>,
    #[serde(default = "Default::default")]
    pub cover_images: Vec<String>,
    pub tags: Option<Vec<ClubTag>>,
    #[serde(default = "serde_default::create_club_favicon")]
    pub favicon: Option<String>,
    #[serde(default = "serde_default::create_club_inner_visible")]
    pub inner_visible: bool,
    #[serde(default = "serde_default::create_club_outer_visible")]
    pub outer_visible: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClubMember {
    id: ObjectId,
    nickname: String,
    avatar: Option<Assets>,
    actor: u8,
}

impl ClubMember {
    pub fn from_user<'d, T: Into<NormalUserModel>, I: Into<&'d ObjectId>>(
        u: T,
        club_id: I,
    ) -> Self {
        let club_id: &ObjectId = club_id.into();
        let user: NormalUserModel = u.into();
        let actor = user
            .actors
            .iter()
            .filter_map(|a| match &a.club_id {
                Some(oid) => {
                    if oid == club_id {
                        Some(a.role as u8)
                    } else {
                        None
                    }
                }
                None => Some(a.role as u8),
            })
            .reduce(|a, b| a | b)
            .unwrap_or(UserRole::Guest as u8);
        Self {
            id: user.id,
            nickname: user.nickname,
            avatar: user.avatar,
            actor,
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClubMemberDTO {
    id: String,
    nickname: String,
    avatar: Option<Assets>,
    actor: u8,
}
impl CreateClubReq {
    pub async fn into_club_model(self, mdb: MDB) -> AppResult<ClubModel> {
        let favicon = {
            if let Some(favicon) = self.favicon {
                if let Ok(oid) = ObjectId::from_str(&favicon) {
                    if let Some(assets) = AssetsModel::find_by_id(&*mdb, oid, None).await? {
                        Some(Assets::from(assets))
                    } else {
                        Some(Assets::new_with_site_path(favicon))
                    }
                } else {
                    Some(Assets::new_with_site_path(favicon))
                }
            } else {
                None
            }
        };
        let cover_images = {
            let t = self
                .cover_images
                .into_iter()
                .map(|image| {
                    Box::pin(async {
                        if let Ok(oid) = ObjectId::from_str(&image) {
                            if let Ok(Some(assets)) =
                                AssetsModel::find_by_id(&*mdb, oid, None).await
                            {
                                Assets::from(assets)
                            } else {
                                Assets::new_with_site_path(image)
                            }
                        } else {
                            Assets::new_with_site_path(image)
                        }
                    })
                })
                .collect::<Vec<_>>();
            let res = futures::future::join_all(t).await;
            res
        };
        let now = DateTime::now();
        let mut model = ClubModel {
            id: None,
            name: self.name,
            primary_color: self.primary_color,
            page: self.page,
            location: self.location,
            address: self.address,
            introduction: self.introduction,
            favicon,
            inner_visible: self.inner_visible,
            outer_visible: self.outer_visible,
            tags: self.tags.unwrap_or_default(),
            cover_images,
            created_at: now,
            updated_at: now,
            ..Default::default()
        };
        model.update_word();
        Ok(model)
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateClubReq {
    pub name: Option<String>,
    pub primary_color: Option<String>,
    pub page: Option<PageModel>,
    pub location: Option<String>,
    pub address: Option<String>,
    pub introduction: Option<String>,
    pub favicon: Option<String>,
    pub inner_visible: Option<bool>,
    pub outer_visible: Option<bool>,
    pub cover_images: Option<Vec<String>>,
}
impl UpdateClubReq {
    pub async fn to_document(self, db: MDB) -> AppResult<Document> {
        let mut res_doc = option_doc!(
            "name":self.name,
            "primary_color":self.primary_color,
            "page":self.page,
            "location":self.location,
            "address":self.address,
            "introduction":self.introduction,
            // "favicon":u.favicon,
            "inner_visible":self.inner_visible
            // "cover_images":u.cover_images
        );
        if let Some(ref name) = self.name {
            res_doc.insert("name_word", text_cut_all(name));
        }
        if let Some(ref introduction) = self.introduction {
            res_doc.insert("introduction_word", text_cut_all(introduction));
        }
        if let Some(cover_images) = self.cover_images {
            let iter = cover_images
                .into_iter()
                .map(|img_str| match ObjectId::from_str(&img_str) {
                    Ok(oid) => {
                        let c_db = db.clone();
                        let fu: Pin<Box<dyn futures::Future<Output = Assets>>> =
                            Box::pin(async move {
                                if let Ok(Some(assets_model)) =
                                    AssetsModel::find_by_id(&*c_db, oid, None).await
                                {
                                    Assets::from(assets_model)
                                } else {
                                    Assets::new_with_site_path(img_str)
                                }
                            });
                        fu
                    }
                    Err(_) => Box::pin(futures::future::ready(Assets::new_with_site_path(img_str))),
                })
                .collect::<Vec<_>>();
            let res = futures::future::join_all(iter).await;
            res_doc.insert("cover_images", res);
        }
        if let Some(favicon) = self.favicon {
            match ObjectId::from_str(&favicon) {
                Ok(oid) => {
                    if let Ok(Some(assets_model)) = AssetsModel::find_by_id(&*db, oid, None).await {
                        res_doc.insert("favicon", Assets::from(assets_model));
                    } else {
                        res_doc.insert("favicon", Assets::new_with_site_path(favicon));
                    }
                }
                Err(_) => {
                    res_doc.insert("favicon", Assets::new_with_site_path(favicon));
                }
            }
        }
        Ok(res_doc)
    }
}

impl From<ClubMember> for ClubMemberDTO {
    fn from(member: ClubMember) -> Self {
        Self {
            id: member.id.to_string(),
            nickname: member.nickname,
            avatar: member.avatar,
            actor: member.actor,
        }
    }
}

/// ## 创建社团活动请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateClubActivitySvrReq {
    pub club_id: ObjectId,
    pub form_id: Option<ObjectId>,
    pub name: String,
    pub description: Option<String>,
    pub page: Option<PageModel>,
    pub location: ClubActivityLocation,
    pub beginning_time: DateTime,
    pub ended_time: Option<DateTime>,
}
impl From<CreateClubActivitySvrReq> for ClubActivityModel {
    fn from(req: CreateClubActivitySvrReq) -> Self {
        Self {
            club_id: req.club_id,
            form_id: req.form_id,
            name: req.name,
            description: req.description,
            page: req.page,
            location: req.location,
            beginning_time: req.beginning_time,
            ended_time: req.ended_time,
            ..Default::default()
        }
    }
}
/// ## 更新社团活动请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateClubActivitySvrReq {
    pub activity_id: ObjectId,
    pub form_id: UpdateField<ObjectId>,
    pub name: Option<String>,
    pub description: UpdateField<String>,
    pub page: UpdateField<PageModel>,
}
impl UpdateModelData for UpdateClubActivitySvrReq {
    type Error = AppError;
    fn get_update_filter_doc(&self) -> Result<Document, Self::Error> {
        Ok(doc! {
            "_id":self.activity_id
        })
    }
    fn get_update_set_doc(&self) -> Result<Document, Self::Error> {
        let mut document = Document::new();
        if let Some(form_id) = self.form_id.to_bson()? {
            document.insert("form_id", form_id);
        }
        if let Some(ref new_name) = self.name {
            document.insert("name", new_name);
        }
        if let Some(description) = self.description.to_bson()? {
            document.insert("description", description);
        }
        if let Some(page) = self.page.to_bson()? {
            document.insert("page", page);
        }

        Ok(document)
    }
}
